package cn.itcast.hotel.service.impl;

import cn.itcast.hotel.mapper.HotelMapper;
import cn.itcast.hotel.pojo.Hotel;
import cn.itcast.hotel.pojo.HotelDoc;
import cn.itcast.hotel.pojo.PageResult;
import cn.itcast.hotel.pojo.RequestParams;
import cn.itcast.hotel.repository.HotelRepository;
import cn.itcast.hotel.service.IHotelService;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


@Service
@Slf4j
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {
    @Autowired
    private RestHighLevelClient restHighLevelClient;
    @Autowired
    private HotelRepository hotelRepository;

    @Override
    public PageResult search(RequestParams params) throws Exception {
        SearchRequest searchRequest = new SearchRequest("hotel");
        buildBasicQuery(params, searchRequest);
        //searchRequest.source().sort("price", SortOrder.DESC);  //分页
        if (StringUtils.isNotBlank(params.getLocation())) {
            searchRequest.source().sort(SortBuilders.geoDistanceSort("location", new GeoPoint(params.getLocation())).unit(DistanceUnit.KILOMETERS).order(SortOrder.ASC));
        }
        Integer page = params.getPage();
        Integer size = params.getSize();
        searchRequest.source().from((page - 1) * size);  //查询第二页
        searchRequest.source().size(size);   //个数
        HighlightBuilder highlightBuilder = new HighlightBuilder();   //高亮查询
        highlightBuilder.field("name");
        highlightBuilder.preTags("<em style='color:red'>");
        highlightBuilder.postTags("</em>");
        highlightBuilder.requireFieldMatch(false); //高亮的字段name和查询的字段all不一样
        searchRequest.source().highlighter(highlightBuilder);
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        return handlerResponse(searchResponse);
    }



    private void buildBasicQuery(RequestParams params, SearchRequest searchRequest) {
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        if (StringUtils.isNotBlank(params.getKey())) {
            boolQuery.must(QueryBuilders.matchQuery("all", params.getKey()));
        } else {
            boolQuery.must(QueryBuilders.matchAllQuery());
        }
        if (StringUtils.isNotBlank(params.getCity())) {
            boolQuery.filter(QueryBuilders.termQuery("city", params.getCity()));
        }
        if (StringUtils.isNotBlank(params.getBrand())) {
            boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
        }
        if (StringUtils.isNotBlank(params.getStarName())) {
            boolQuery.filter(QueryBuilders.termQuery("startName", params.getStarName()));
        }
        if (params.getMinPrice() != null && params.getMaxPrice() != null) {
            boolQuery.filter(QueryBuilders.rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));
        }
        //searchRequest.source().query(boolQuery);
        FunctionScoreQueryBuilder functionScoreQuery =
                QueryBuilders.functionScoreQuery(
                        // 原始查询，相关性算分的查询
                        boolQuery,
                        // function score的数组
                        new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                                // 其中的一个function score 元素
                                new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                        // 过滤条件
                                        QueryBuilders.termQuery("isAD", true),
                                        // 算分函数
                                        ScoreFunctionBuilders.weightFactorFunction(10)
                                )
                        });
        searchRequest.source().query(functionScoreQuery);
    }

    private PageResult handlerResponse(SearchResponse searchResponse) {
        List<HotelDoc> hotels = new ArrayList<>();
        SearchHits searchHits = searchResponse.getHits();
        System.out.println("查询到的总条数：" + searchHits.getTotalHits().value);
        SearchHit[] hits = searchHits.getHits();
        for (SearchHit hit : hits) {
            String jsonString = hit.getSourceAsString();
            HotelDoc hotelDoc = JSON.parseObject(jsonString, HotelDoc.class);
            //处理高亮
            Map<String, HighlightField> highlightFields = hit.getHighlightFields();
            if (highlightFields != null) {
                HighlightField highlightField = highlightFields.get("name");
                if (highlightField != null) {
                    Text[] fragments = highlightField.fragments();
                    if (fragments != null && fragments.length > 0) {
                        String nameHighLight = fragments[0].toString();
                        hotelDoc.setName(nameHighLight);
                    }
                }
            }
            Object[] sortValues = hit.getSortValues();
            if (sortValues != null && sortValues.length > 0) {
                hotelDoc.setDistance(sortValues[0]);
            }
            hotels.add(hotelDoc);
        }
        PageResult pageResult = new PageResult(searchHits.getTotalHits().value, hotels);
        return pageResult;
    }

    @Override
    public Map filters(RequestParams params)throws Exception {
        SearchRequest searchRequest = new SearchRequest("hotel");
        buildBasicQuery(params, searchRequest);
        searchRequest.source().size(0);
        searchRequest.source().aggregation(AggregationBuilders.terms("brandAgg").field("brand").size(20));
        searchRequest.source().aggregation(AggregationBuilders.terms("cityAgg").field("city").size(20));
        searchRequest.source().aggregation(AggregationBuilders.terms("starNameAgg").field("starName").size(20));
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        Aggregations aggregations = searchResponse.getAggregations();

        List<String> starNameList = new ArrayList<>();
        Terms starNameTerms = aggregations.get("starNameAgg");
        List<? extends Terms.Bucket> buckets = starNameTerms.getBuckets();
        for (Terms.Bucket bucket : buckets) {
            starNameList.add(bucket.getKeyAsString());
        }

        List<String> cityList = new ArrayList<>();
        Terms cityTerms = aggregations.get("cityAgg");
        List<? extends Terms.Bucket> cityBuckets = cityTerms.getBuckets();
        for (Terms.Bucket bucket : cityBuckets) {
            cityList.add(bucket.getKeyAsString());
        }

        List<String> brandList = new ArrayList<>();
        Terms brandTerms = aggregations.get("brandAgg");
        List<? extends Terms.Bucket> brandBuckets = brandTerms.getBuckets();
        for (Terms.Bucket bucket : brandBuckets) {
            brandList.add(bucket.getKeyAsString());
        }

        Map resultMap = new HashMap();
        resultMap.put("city", cityList);
        resultMap.put("starName", starNameList);
        resultMap.put("brand", brandList);
        return resultMap;
    }

    @Override
    public void saveOrUpdateEs(String hotelJsonString) {
        Hotel hotel = JSON.parseObject(hotelJsonString, Hotel.class);
        HotelDoc hotelDoc = new HotelDoc(hotel);
        IndexRequest request = new IndexRequest("hotel"); //ES索引库的名字
        request.id(hotel.getId().toString());
        String jsonString = JSON.toJSONString(hotelDoc);
        request.source(jsonString, XContentType.JSON);
        try {
            IndexResponse indexResponse = restHighLevelClient.index(request, RequestOptions.DEFAULT);
            log.info("数据新增或修改后，es中的数据同步成功");
        } catch (Exception e) {
            log.error("数据新增或修改后，es中的数据同步失败");
            e.printStackTrace();
        }
    }

    @Override
    public void deleteEs(String idString) {
        DeleteRequest deleteRequest = new DeleteRequest("hotel", idString);
        try {
            DeleteResponse deleteResponse = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
            log.info("数据删除后，es中的数据同步成功");
        } catch (Exception e) {
            log.error("数据删除后，es中的数据同步失败");
            e.printStackTrace();
        }
    }
}
